// UnitAI.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;

using Supremacy.Diplomacy;
using Supremacy.Entities;
using Supremacy.Game;
using Supremacy.Orbitals;
using Supremacy.Universe;

using Wintellect.PowerCollections;

namespace Supremacy.AI
{
    public static class UnitAI
    {
        public static int GetAttackOdds(Fleet fleet, Sector sector, bool potentialEnemy)
        {
            int ourStrength;
            int theirStrength = 0;
            int ourFirepower;
            int theirFirepower = 0;
            int baseOdds;
            int strengthFactor;
            int damageToUs;
            int damageToThem;
            int neededRoundsUs;
            int neededRoundsThem;
            int neededRoundsDiff;
            int finalOdds;
            IList<Orbital> defenders = GetDefenders(null, fleet, !potentialEnemy, potentialEnemy, false);

            if (defenders.Count == 0)
                return 100;

            ourStrength = GetCombatStrength(fleet);
            ourFirepower = GetFirepower(fleet);

            if (ourStrength == 0)
                return 1;

            foreach (Orbital defender in defenders)
            {
                theirStrength += GetCombatStrength(defender);
                theirFirepower += GetFirepower(defender);
            }

            baseOdds = (100 * ourStrength) / (ourStrength + theirStrength);

            if (baseOdds == 0)
                return 1;

            strengthFactor = ((ourFirepower + theirFirepower + 1) / 2);

            damageToUs = Math.Max(1, (theirFirepower + strengthFactor) / (ourFirepower + strengthFactor));
            damageToThem = Math.Max(1, (ourFirepower + strengthFactor) / (theirFirepower + strengthFactor));

            neededRoundsUs = (GetTotalHitPoints(defenders) + damageToThem - 1) / damageToThem;
            neededRoundsThem = (GetTotalHitPoints(fleet) + damageToUs - 1) / damageToUs;

            neededRoundsDiff = (neededRoundsUs - neededRoundsThem);
            if (neededRoundsDiff > 0)
            {
                theirStrength *= (1 + neededRoundsDiff);
            }
            else
            {
                ourStrength *= (1 - neededRoundsDiff);
            }

            finalOdds = ((ourStrength * 100) / (ourStrength + theirStrength));
            finalOdds += (((100 - finalOdds) * GetWithdrawalProbability(fleet)) / 100);
            //finalOdds += getAttackOddsChange for player

            return Math.Max(1, Math.Min(finalOdds, 99));
        }

        private static int GetWithdrawalProbability(Fleet fleet)
        {
            if (fleet == null)
                throw new ArgumentNullException("fleet");
            int nonCombatantCount = 0;
            foreach (Ship ship in fleet.Ships)
            {
                if (!ship.IsCombatant)
                    nonCombatantCount++;
            }
            return (100 * (int)(nonCombatantCount / (double) fleet.Ships.Count));
        }

        public static IList<Orbital> GetDefenders(Civilization owner, Fleet attacker, bool testAtWar, bool testPotentialEnemy, bool testCanMove)
        {
            List<Orbital> defenders = new List<Orbital>();
            foreach (Orbital defender in GameContext.Current.Universe.FindAt<Orbital>(attacker.Location))
            {
                if ((owner == null) || (defender.Owner == owner))
                {
                    if (IsVisible(defender, owner))
                    {
                        if (!testPotentialEnemy || IsPotentialEnemy(attacker.Owner, defender.Owner))
                        {
                            if (!testAtWar || IsAtWar(attacker.Owner, defender.Owner))
                            {
                                if (!testCanMove || defender.CanMove)
                                {
                                    defenders.Add(defender);
                                }
                            }
                        }
                    }
                }
            }
            return defenders;
        }

        public static bool IsAtWar(Civilization source, Civilization target)
        {
            return (GetRelationshipStatus(source, target) == RelationshipStatus.AtWar);
        }

        public static bool IsPotentialEnemy(Civilization source, Civilization target)
        {
            switch (GetRelationshipStatus(source, target))
            {
                case RelationshipStatus.AtWar:
                case RelationshipStatus.Neutral:
                case RelationshipStatus.Unknown:
                    return true;
                default:
                    return false;
            }
        }

        public static RelationshipStatus GetRelationshipStatus(Civilization source, Civilization target)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (source == null)
                throw new ArgumentNullException("target");
            if (source == target)
                return RelationshipStatus.Self;
            try
            {
                Relationship relationship = GameContext.Current.Relationships[source, target];
                if (relationship != null)
                {
                    return relationship.Status;
                }
            }
            catch {}
            return RelationshipStatus.Unknown;
        }

        public static bool IsVisible(UniverseObject unit, Civilization civ)
        {
            if (unit == null)
            {
                throw new ArgumentNullException("unit");
            }
            if (unit is Fleet)
            {
                return FleetView.Create(civ, (Fleet)unit).IsPresenceKnown;
            }
            else
            {
                try
                {
                    CivilizationManager civManager = GameContext.Current.CivilizationManagers[civ];
                    if ((civManager != null) && (civManager.MapData != null))
                    {
                        return civManager.MapData.IsExplored(unit.Location);
                    }
                }
                catch {}
            }
            return false;
        }

        public static int GetEffectiveCombatStrength(Fleet fleet)
        {
            int totalStrength = 0;
            foreach (Ship ship in fleet.Ships)
                totalStrength += GetEffectiveCombatStrength(ship);
            return totalStrength;
        }

        public static int GetEffectiveCombatStrength(Orbital orbital)
        {
            int effectiveStrength = GetCombatStrength(orbital);
            effectiveStrength *= (orbital.ShieldStrength.Maximum 
                + orbital.ShieldStrength.CurrentValue
                + orbital.HullStrength.Maximum
                + orbital.HullStrength.CurrentValue);
            effectiveStrength *= ((2 * orbital.ShieldStrength.Maximum)
                                  + (2 * orbital.HullStrength.Maximum));
            return effectiveStrength;
        }

        public static int GetCombatStrength(Fleet fleet)
        {
            if (fleet == null)
                throw new ArgumentNullException("fleet");
            int totalStrength = 0;
            foreach (Ship ship in fleet.Ships)
                totalStrength += GetCombatStrength(ship);
            return totalStrength;
        }

        public static int GetCombatStrength(Orbital orbital)
        {
            if (orbital == null)
                throw new ArgumentNullException("orbital");
            int strength = 0;
            if (orbital.OrbitalDesign.PrimaryWeapon != null)
            {
                strength += (orbital.OrbitalDesign.PrimaryWeapon.Damage
                             * orbital.OrbitalDesign.PrimaryWeapon.Count);
            }
            if (orbital.OrbitalDesign.SecondaryWeapon != null)
            {
                strength += (orbital.OrbitalDesign.SecondaryWeapon.Damage
                             * orbital.OrbitalDesign.SecondaryWeapon.Count);
            }
            strength *= (orbital.ShieldStrength.CurrentValue + orbital.HullStrength.CurrentValue);
            strength /= (orbital.ShieldStrength.Maximum + orbital.HullStrength.Maximum);
            return strength;
        }

        public static int GetFirepower(Fleet fleet)
        {
            if (fleet == null)
                throw new ArgumentNullException("fleet");
            int totalFirepower = 0;
            foreach (Ship ship in fleet.Ships)
                totalFirepower += GetFirepower(ship);
            return totalFirepower;
        }

        public static int GetFirepower(Orbital orbital)
        {
            if (orbital == null)
                throw new ArgumentNullException("orbital");
            int firepower = 0;
            if (orbital.OrbitalDesign.PrimaryWeapon != null)
            {
                firepower += (orbital.OrbitalDesign.PrimaryWeapon.Damage
                              * orbital.OrbitalDesign.PrimaryWeapon.Count);
            }
            if (orbital.OrbitalDesign.SecondaryWeapon != null)
            {
                firepower += (orbital.OrbitalDesign.SecondaryWeapon.Damage
                              * orbital.OrbitalDesign.SecondaryWeapon.Count);
            }
            return firepower;
        }

        public static int GetTotalHitPoints(Orbital orbital)
        {
            if (orbital == null)
                throw new ArgumentNullException("orbital");
            return (orbital.HullStrength.CurrentValue + orbital.ShieldStrength.CurrentValue);
        }

        public static int GetTotalHitPoints(IEnumerable<Orbital> orbitals)
        {
            if (orbitals == null)
                throw new ArgumentNullException("orbitals");
            int totalHitPoints = 0;
            foreach (Orbital orbital in orbitals)
                totalHitPoints += GetTotalHitPoints(orbital);
            return totalHitPoints;
        }

        public static int GetTotalHitPoints(Fleet fleet)
        {
            if (fleet == null)
                throw new ArgumentNullException("fleet");
            int totalHitPoints = 0;
            foreach (Ship ship in fleet.Ships)
                totalHitPoints += GetTotalHitPoints(ship);
            return totalHitPoints;
        }

        public static bool GetBestSystemToColonize(Civilization owner, MapLocation origin, int radius, out StarSystem result)
        {
            List<StarSystem> systems = new List<StarSystem>(GameContext.Current.Universe.Find<StarSystem>());
            for (int i = 0; i < systems.Count; i++)
            {
                if (systems[i].IsInhabited || (systems[i].IsOwned && (systems[i].Owner != owner)))
                    systems.RemoveAt(i--);
            }
            systems.Sort(
                delegate(StarSystem a, StarSystem b)
                {
                    foreach (Orbital orbital in GameContext.Current.Universe.FindAt<Orbital>(a.Location))
                    {
                        if (IsPotentialEnemy(owner, orbital.Owner))
                            return -1;
                    }
                    return (int) ((a.GetMaxPopulation(owner.Race) * a.GetGrowthRate(owner.Race))
                                  - (b.GetMaxPopulation(owner.Race) * b.GetGrowthRate(owner.Race)));
                });
            if (systems.Count > 0)
            {
                result = systems[systems.Count - 1];
                return true;
            }
            result = null;
            return false;
        }

        public static bool CanEnterSector(MapLocation location, Civilization civ)
        {
            return CanEnterSector(GameContext.Current.Universe.Map[location], civ);
        }

        public static bool CanEnterSector(Sector sector, Civilization civ)
        {
            if (sector == null)
                throw new ArgumentNullException("sector");
            if (civ == null)
                throw new ArgumentNullException("civ");
            if (!sector.IsOwned)
                return true;
            if (sector.Owner == civ)
                return true;
            return GameContext.Current.Relationships[civ, sector.Owner].AreBordersOpen;
        }

        public static bool IsEnemyUnitVisible(Sector sector, Civilization civ)
        {
            if (sector == null)
                throw new ArgumentNullException("sector");
            return IsEnemyUnitVisible(sector.Location, civ);
        }

        public static bool IsEnemyUnitVisible(MapLocation location, Civilization civ)
        {
            if (civ == null)
                throw new ArgumentNullException("civ");
            foreach (Orbital orbital in GameContext.Current.Universe.FindAt<Orbital>(location))
            {
                if (IsPotentialEnemy(civ, orbital.Owner))
                    return true;
            }
            return false;
        }

        public static int GetExploreValue(MapLocation location, Fleet fleet)
        {
            return GetExploreValue(GameContext.Current.Universe.Map[location], fleet);
        }

        public static IEnumerable<Sector> GetSectorsVisibleToFleet(Fleet fleet, Sector sector)
        {
            if (sector == null)
                throw new ArgumentNullException("sector");
            return GetSectorsVisibleToFleet(fleet, sector.Location);
        }

        public static IEnumerable<Sector> GetSectorsVisibleToFleet(Fleet fleet, MapLocation location)
        {
            Set<Sector> sectors = new Set<Sector>();
            SectorMap map = GameContext.Current.Universe.Map;
            int startX = Math.Max(0, location.X - fleet.SensorRange);
            int startY = Math.Max(0, location.Y - fleet.SensorRange);
            int endX = Math.Min(map.Width - 1, location.X + fleet.SensorRange);
            int endY = Math.Min(map.Height - 1, location.Y + fleet.SensorRange);
            for (int x = startX; x < endX; x++)
            {
                for (int y = startY; y < endY; y++)
                {
                    sectors.Add(map[x, y]);
                }
            }
            return sectors;
        }

        public static int GetExploreValue(Sector sector, Fleet fleet)
        {
            if (sector == null)
                throw new ArgumentNullException("sector");
            if (fleet == null)
                throw new ArgumentNullException("fleet");
            
            if (!fleet.IsOwned)
                return 0;

            int value = 0;
            int extraValue = 0;

            CivilizationManager civManager = GameContext.Current.CivilizationManagers[fleet.Owner];
            CivilizationMapData mapData = civManager.MapData;

            if (!CanEnterSector(sector, fleet.Owner))
                return 0;

            if (!FleetHelper.IsSectorWithinFuelRange(sector, fleet))
                return 0;

            if (!mapData.IsScanned(sector.Location))
                value += 5000;

            if (!mapData.IsExplored(sector.Location))
            {
                if (sector.IsOwned && (sector.Owner != fleet.Owner))
                    value += 2000;
                if (sector.System != null)
                    extraValue += 50000;
            }

            foreach (Sector otherSector in GetSectorsVisibleToFleet(fleet, sector))
            {
                if (!mapData.IsExplored(otherSector.Location))
                {
                    if (!mapData.IsScanned(otherSector.Location))
                    {
                        value += 1000;
                        if (otherSector.IsOwned && (otherSector.Owner != fleet.Owner))
                            value += 2000;
                    }
                    if ((otherSector.System != null) && (otherSector.System.Owner != fleet.Owner))
                    {
                        extraValue += 10000;
                    }
                }
            }

            if (value > 0)
            {
                //value += Statistics.Random(3000);
                value += extraValue;
            }

            // Explore the area around the home system first.
            if (civManager.Colonies.Count == 1)
            {
                Colony homeColony = civManager.HomeColony;
                if ((homeColony != null) && (homeColony.Owner == fleet.Owner))
                {
                    int distance = MapLocation.GetDistance(sector.Location, homeColony.Location);
                    if (distance > 8)
                    {
                        value /= (distance > 16) ? 5 : 3;
                    }
                }
            }

            return value;
        }

        public static int GetExploreTurnValue(Fleet fleet)
        {
            if (fleet == null)
                throw new ArgumentNullException("fleet");

            int value = 0;
            int sectorCount = 0;
            CivilizationMapData mapData = GameContext.Current.CivilizationManagers[fleet.Owner].MapData;

            foreach (Sector sector in MapHelper.GetSectorsWithinRadius(fleet.Sector, fleet.SensorRange))
            {
                if (!mapData.IsExplored(sector.Location))
                {
                    value += GetExploreValue(sector, fleet);
                    sectorCount++;
                }
            } 

            if (sectorCount > 0)
            {
                value /= ((sectorCount + 2) / 3);
            }

            return value;
        }
    }
}
